#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import sys
import string
import pickle
import urllib
import pynotify
from xml.dom import minidom

import pygtk
pygtk.require('2.0')
import gtk
import gtk.glade
import gobject

APP_NAME = 'bendanchi'
LOCALE_DIR = 'locale'

import locale
import gettext

locale.setlocale(locale.LC_ALL, '')
gettext.bindtextdomain(APP_NAME, LOCALE_DIR)
gettext.bind_textdomain_codeset(APP_NAME, "UTF-8")
gettext.textdomain(APP_NAME)
_ = gettext.gettext

BUT_LEFT, BUT_MID, BUT_RIGHT = 1, 2, 3

LOGO = os.path.join(os.path.realpath(os.path.dirname(__file__)), 'logo.png')
GLADE = os.path.join(os.path.realpath(os.path.dirname(__file__)), 'bendanchi.glade')

REPOSITORY = os.path.expanduser('~/.bendanchi')

SYSTRAY_TOOLTIP = _('<b><i>BenDanChi</i></b> L:Search, L+S:FastSearch, L+C:AddWord, M:ToggleRecite, R:ToggleMainWin')

class Notifier:
    def __init__(self, app_name = APP_NAME):
        pynotify.init(app_name)
        self.noti = pynotify.Notification(' ', ' ')
        self.noti.set_urgency(pynotify.URGENCY_CRITICAL)

    def msg(self, title, message, timeout):
        """ timeout and markup doesn't work in ubuntu notify """
        title = _('BenDanChi: ') + title
        self.noti.update(title, message, LOGO)
        self.noti.set_timeout(timeout)
        self.noti.show()

    def new(self, query):
        if query != []:
            word, audio, pron, defs, sent = query

            title = word
            message = ''
            if pron != None and pron != '':
                message += '<i>[' + pron + ']</i>\n'
            if defs != None and defs != '':
                message += '<i>' + defs + '</i>\n'
            if audio != None and audio != '':
                os.popen3('mplayer "' + audio + '"')

            i = 1
            for s in sent:
                message += '<i>' + str(i) + ': ' + s[0] + '</i>\n'
                message += '<i>' + str(i) + ': ' + s[1] + '</i>\n'
                i += 1

                # too many sentence can make the text above shows as ...
                break

            self.msg(title, message, 5)

class DictSearch:
    def __init__(self):
        self.query_string = ''
        pass

    def _build_query(self, word):
        self.query_string = r'http://dict.cn/ws.php?utf-8=ture&q=%s' % word

    def parse_query(self, xml_string):
        """ return [word, audio, pron, defs, [[org, trans], ]] """
        xmldoc = minidom.parseString(xml_string)

        word = audio = pron = defs = None
        sent = []

        ele = xmldoc.getElementsByTagName('def')
        if ele != []:
            defs = ele[0].childNodes[0].nodeValue

            if defs == u'Not Found':
                return []

        ele = xmldoc.getElementsByTagName('key')
        if ele != []:
            word = ele[0].childNodes[0].nodeValue

        ele = xmldoc.getElementsByTagName('audio')
        if ele != []:
            audio = ele[0].childNodes[0].nodeValue

        ele = xmldoc.getElementsByTagName('pron')
        if ele != []:
            pron = ele[0].childNodes[0].nodeValue

        ele = xmldoc.getElementsByTagName('sent')
        if ele != []:
            for e in ele:
                sent.append([e.childNodes[0].childNodes[0].nodeValue, e.childNodes[1].childNodes[0].nodeValue])

        return [word, audio, pron, defs, sent]

    def query(self, word):
        self._build_query(word)

        try:
            f = urllib.urlopen(self.query_string)
            ctt = f.read()
            f.close()
        except:
            return []

        # should get the encoding then decode to utf-8
        if ctt.find(r'encoding="GBK') > 0:
            ctt = ctt.replace('GBK', 'utf-8')
            ctt = ctt.decode('GBK')
        return self.parse_query(ctt)

class Mainwin:
    def __init__(self):
        self.glade = gtk.glade.XML(GLADE)

        self.mainwin = self.get_widget('window_book')
        self.mainwin.set_title(_('BenDanChi'))
        ui_logo = gtk.gdk.pixbuf_new_from_file(LOGO)
        self.mainwin.set_icon(ui_logo)
        self.mainwin.connect('delete_event', self.on_quit)

        self._setup_word_list()

        self.word_detail = self.get_widget('textview_word_details')

        self.play_button = self.get_widget('button_play')
        self.play_button.connect('clicked', self._on_play)

        self.fast_search = self.get_widget('window_fast_search')
        self.entry_input = self.get_widget('entry_input')
        self.entry_input.connect('activate', self._on_entry_input_activate)
        self.entry_input.connect('key-press-event', self._on_entry_input_key_press)
        self.entry_input.connect('focus-out-event', self.focus_out_cb)
        self.systray = None
        self._setupSystray()

        self.dict_searcher = DictSearch()
        self.notifier = Notifier()

        self.word_list = []
        self.word_index = 0

        self.dictionary = {}
        self.current_word = ''

        self.recite_mode = False

        # load word recorders
        try:
            file = open(REPOSITORY, 'r')
            self.dictionary = pickle.load(file)
            file.close()
        except:
            pass

        for key in self.dictionary.keys():
            self.word_list_store.append([key])
            self.word_list.append(key)

    def _format_word(self, word, by = u' '):
            delstr = unicode(string.punctuation + string.digits + u'《》（）&%￥#@！{}【】')
            word = unicode(word)
            out = u''
            for c in word:
                if not c in delstr:
                    out += c
                else:
                    out += by
            return out

    def _on_recite(self):
        if self.recite_mode:
            word_list_len = len(self.word_list)

            if word_list_len > 0:
                index = self.word_index % word_list_len

                query = self.dictionary[self.word_list[index]]
                self.notifier.new(query)
                self.word_index += 1

            self.recite_timer = gobject.timeout_add_seconds(10, self._on_recite)

    def _on_entry_input_key_press(self, widget, key):
        if key.keyval == gtk.keysyms.Escape:
            self._hide_fast_search()

    def focus_out_cb(self, entry, event):
        self._hide_fast_search()

    def _on_entry_input_activate(self, widget):
        clipboard = gtk.clipboard_get('PRIMARY')
        text = self.entry_input.get_text()
        clipboard.set_text(text)
        self._hide_fast_search()
        self._cb_clipboard_get_text(clipboard, text, ('PRIMARY', BUT_LEFT, 0))

    def _on_play(self, widget):
        if self.dictionary.has_key(self.current_word):
            word, audio, pron, defs, sent = self.dictionary[self.current_word]
            if audio != None and audio != '':
                os.popen3('mplayer "' + audio + '"')

    def _add_word_list_col(self, title, columnId):
        """This function adds a column to the list view.
        First it create the gtk.TreeViewColumn and then set
        some needed properties"""

        column = gtk.TreeViewColumn(title, gtk.CellRendererText(), text = columnId)
        column.set_resizable(True)
        column.set_sort_column_id(columnId)
        self.word_listview.append_column(column)

    def _word_list_foreach(self, model, path, iter, selected):
        selected.append(model.get_value(iter, 0))

    def _open_word_list_item(self, event):
        selected = []
        self.word_listview.get_selection().selected_foreach(self._word_list_foreach, selected)
        for word in selected:
            if self.dictionary.has_key(word):
                word, audio, pron, defs, sent = self.dictionary[word]
                text_buffer = gtk.TextBuffer()

                message = 'Word: ' + word + '\n'
                if pron != None and pron != '':
                    message += 'Pron: ' + pron + '\n'
                if defs != None and defs != '':
                    message += 'Defs: ' + defs + '\n'

                message += '\n'
                i = 1
                for s in sent:
                    message += str(i) + ': ' + s[0] + '\n'
                    message += str(i) + ': ' + s[1] + '\n'
                    message += '\n'
                    i += 1

                text_buffer.set_text(message)
                self.word_detail.set_buffer(text_buffer)

                self.current_word = word

    def _on_list_selection_changed(self, select):
        self._open_word_list_item(None)

    def _on_word_listview_key_press(self, widget, key):
        if key.keyval == gtk.keysyms.Delete:
            it = self.word_listview.get_selection().get_selected()[1]
            if it:
                word = self.word_list_store.get(it, 0)[0]
                self.word_list.remove(word)
                del(self.dictionary[word])
                self.word_list_store.remove(it)

                file = open(REPOSITORY, 'w')
                pickle.dump(self.dictionary, file)
                file.close()

    def _setup_word_list(self):
        self.word_listview = self.get_widget('treeview_wordlist')
        # Add List Columns
        self._add_word_list_col(_('Words'), 0)

        self.word_list_store = gtk.ListStore(str)
        self.word_listview.set_model(self.word_list_store)

        sel = self.word_listview.get_selection()
        sel.connect('changed', self._on_list_selection_changed)

        self.word_listview.connect('key-press-event', self._on_word_listview_key_press)

    def get_widget(self, name):
        return self.glade.get_widget(name)

    def _setupSystray(self):
        self.systray = gtk.StatusIcon()

        self.systray.set_from_file(LOGO)
        self.systray.connect('button-press-event', self._on_systray_button_press)
        self.systray.set_tooltip_markup(SYSTRAY_TOOLTIP)
        self.systray.set_visible(True)

    def query_local(self, text):
        if self.dictionary.has_key(text):
            return self.dictionary[text]
        else:
            return []

    def save_local(self, query):
        OK, NG, ALREADY = 1, 2, 3
        if query != []:
            word, audio, pron, defs, sent = query
            if not self.dictionary.has_key(word):
                self.dictionary[word] = query

                file = open(REPOSITORY, 'w')
                pickle.dump(self.dictionary, file)
                file.close()

                self.word_list_store.append([word])
                self.word_list.append(word)

                return OK
            return ALREADY
        return NG

    def _cb_clipboard_get_text(self, clipboard, text, data):
        clp_type, button, state = data

        if text == None and clp_type == 'PRIMARY':
            clipboard = gtk.clipboard_get('CLIPBOARD')
            clipboard.request_text(self._cb_clipboard_get_text, ('CLIPBOARD', button, state))
            return

        if text != None:
            text = self._format_word(text, ' ')
            text = text.strip().lower()

            query = self.query_local(text)
            if query == []:
                query = self.dict_searcher.query(text)

            if button == BUT_LEFT and int(state) & int(gtk.gdk.CONTROL_MASK):
                # add words
                ret = self.save_local(query)
                OK, NG, ALREADY = 1, 2, 3
                if ret == OK:
                    self.notifier.msg(_('Congratulation'), _('Word: %s added!') % text, 3)
                elif ret == NG:
                    self.notifier.msg(_('Error'), _('Word: %s: error or trans not found!') % text, 3)
                elif ret == ALREADY:
                    self.notifier.msg(_('Notice'), _('Word: %s already added, skip!') % text, 3)
            elif button == BUT_LEFT:
                if query == []:
                    self.notifier.msg(_('Error'), _('Word: %s not found!') % text, 3)
                else:
                    self.notifier.new(query)

    def _enter_recite_mode(self):
        self.recite_mode = True
        self.recite_timer = gobject.timeout_add_seconds(1, self._on_recite)

    def _leave_recite_mode(self):
        self.recite_mode = False

    def _show_fast_search(self):
        self.entry_input.set_text('')
        self.fast_search.show()

    def _hide_fast_search(self):
        self.fast_search.hide()

    def _on_systray_button_press(self, widget, event):
        '''Actions: showMainWindow, search, add, fastSearch, recite mode

        Left:Search, Left+Ctrl:Add, Left+Shift:FastSearch, Right:ToggleMain, Mid:ReciteMode'''

        if event.button == BUT_MID and not self.recite_mode:
            self._enter_recite_mode()
        else:
            self._leave_recite_mode()

        if event.button == BUT_RIGHT:
            self._toggle_main_window()
        elif event.button == BUT_LEFT:
            if int(event.state) & int(gtk.gdk.SHIFT_MASK):
                self._show_fast_search()
            else:
                clipboard = gtk.clipboard_get('PRIMARY')
                clipboard.request_text(self._cb_clipboard_get_text, ('PRIMARY', event.button, event.state))

    def _toggle_main_window(self):
        if self.mainwin.get_property('visible'):
            self.mainwin.hide()
        else:
            self.mainwin.show()

    def on_quit(self, win, evt = gtk.gdk.DELETE):
        self.quit(win)

    def quit(self, win):
        gtk.main_quit(win)

if __name__ == '__main__':
    Mainwin()
    gtk.main()
